import time
import threading
from quant import utils


class EventEngine:
    all_instance = []

    @classmethod
    def set_show_mode(cls):
        cls.put = cls.put_show

    def __init__(self):
        self.all_instance.append(self)
        self.__event_to_subscribers = {}

    def __repr__(self):
        result = '---------EventEngine---------\n'
        for event, handlers in self.__event_to_subscribers.items():
            result += str(event) + '\n'
            for handler in handlers:
                result += '  ' + str(handler) + '\n'
        return result

    def subscribe(self, event, handler):
        event_subscribers = self.__event_to_subscribers.setdefault(event, [])
        if handler not in event_subscribers:
            event_subscribers.append(handler)

    def unsubscribe(self, event, handler):
        handlers = self.__event_to_subscribers.get(event, None)
        if handlers:
            if handler in handlers:
                handlers.remove(handler)

    def put(self, event, *args, **kwargs):
        handlers = self.__event_to_subscribers.get(event, None)
        if handlers:
            for handler in self.__event_to_subscribers[event]:
                try:
                    handler(*args, **kwargs)
                except:
                    utils.catch_exception()

    def put_show(self, event, *args, **kwargs):
        print('    ->{}.put({})'.format(type(self).__name__, event))
        handlers = self.__event_to_subscribers.get(event, None)
        if handlers:
            for handler in self.__event_to_subscribers[event]:
                print('          to {}'.format(handler))
                try:
                    handler(*args, **kwargs)
                except:
                    utils.catch_exception()


class Timer:
    __init_flag = False

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Timer, cls).__new__(cls)
        return cls.instance

    def __init__(self):
        if self.__class__.__init_flag:
            return
        self.handler_to_second = {}
        self.handler_to_last_do = {}
        self.thread = threading.Thread(target=self.__run, daemon=True)
        self.__class__.__init_flag = True

    def __repr__(self):
        return str(self.handler_to_second)

    def __run(self):
        while True:
            try:
                self.__func()
            except:
                utils.catch_exception()
            time.sleep(Timer_Check_interval)

    def __func(self):
        for handler in list(self.handler_to_last_do.keys()):
            last_do = self.handler_to_last_do[handler]

            second = self.handler_to_second[handler]
            already = time.time() - last_do
            if already >= second:
                self.handler_to_last_do[handler] += second
                try:
                    handler()
                except:
                    utils.catch_exception()

    def register(self, handler, second):
        if second < Timer_Check_interval:
            err = 'Timer interval cannot be less than CHECK_INTERVAL! {}->{}'.format(second, Timer_Check_interval)
            raise ValueError(err)
        if handler not in self.handler_to_second:
            self.handler_to_second[handler] = second
            self.handler_to_last_do[handler] = time.time()
        if not self.thread.is_alive():
            self.thread.start()

    def unregister(self, handler, second):
        if handler in self.handler_to_second:
            del self.handler_to_second[handler]
        if handler in self.handler_to_last_do:
            del self.handler_to_last_do[handler]

    def test_show(self):
        print(self.handler_to_second)
        print(self.handler_to_last_do)


class Timer2:
    CompensateCount = 2
    all_instances = []

    def __init__(self, fn, interval, log_block=True):
        self.interval = interval
        self._fn = utils.except_caught_fn(fn)
        self._log_block = log_block

        self._last_time = None
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._compensate_range = interval * self.CompensateCount
        type(self).all_instances.append(self)

    def start(self):
        self._last_time = time.perf_counter()
        self._thread.start()

    def _run(self):
        while True:
            now = time.perf_counter()
            last_time = self._last_time
            if last_time < now - self._compensate_range:
                utils.logging.error('Timer3(intv={}) blocked for {:.3f}s'.format(self.interval, now-last_time))
                last_time = now - self._compensate_range

            next_time = last_time + self.interval
            sleep = next_time - now
            if sleep > epsilon:
                time.sleep(sleep)

            self._fn()
            self._last_time = next_time


Timer_Check_interval = 0.01
epsilon = 0.0000001


if __name__ == '__main__':

    ee = EventEngine()
    EventEngine.set_show_mode()

    def on():
        pass

    class A:
        def on(self):
            pass


    ee.subscribe('a', on)
    a = A()
    ee.subscribe('a', a.on)


    ee.put('a')














